/**
 *
 */
package com.kjcxlab.avcviewer.video;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
import android.widget.Toast;

import com.kjcxlab.avcviewer.util.Constants;
import com.kjcxlab.avcviewer.video.VideoStreamInfo;
/**
 * @author coder
 */
public class VideoDecoder implements Runnable {

    private ArrayBlockingQueue<byte[]> outQueue = null;
    private MediaCodec mDecoder = null;
    private MediaFormat mFormat = null;
    private ByteBuffer[] inputBuffers = null;
    //private ByteBuffer[] outputBuffers = null;
    private final static long IN_DECODER_TIMEOUT = 1000000;
    private final static long OUT_DECODER_TIMEOUT = 10000;
    private final static String TAG = "Decoder";
    private VideoStreamInfo userStreamInfo = null;
    private static byte[] userSps = null;
    private static byte[] userPps = null;

    public VideoDecoder(ArrayBlockingQueue<byte[]> queue) {
        this.outQueue = queue;
    }

    private boolean configDecoder()
    {

        if (userStreamInfo  == null)
            return false;

        mFormat = MediaFormat.createVideoFormat(userStreamInfo.getMime(),userStreamInfo.getVideoWidth() , userStreamInfo.getVideoHeight());
        if (userStreamInfo.useDefaultConfig())
        {
            if (userStreamInfo.getPpsArray() == null || !userStreamInfo.getMime().startsWith("video/")) {
                return false;
            }
            mFormat.setByteBuffer("csd-0", ByteBuffer.wrap(userStreamInfo.getSpsArray()));
            mFormat.setByteBuffer("csd-1", ByteBuffer.wrap(userStreamInfo.getPpsArray()));
        }
        else
        {
            if ((userSps == null) || (userPps == null))
            {
                return false;
            }
            mFormat.setByteBuffer("csd-0", ByteBuffer.wrap(userSps));
            mFormat.setByteBuffer("csd-1", ByteBuffer.wrap(userPps));
        }

        try {
            mDecoder = MediaCodec.createDecoderByType(userStreamInfo.getMime());
        } catch (IOException e) {
            e.printStackTrace();
        }
        mDecoder.configure(mFormat, userStreamInfo.getSurface() /* surface */, null /* crypto */, 0 /* flags */);

        if (mDecoder == null) {
            Log.e(TAG, "Can't find video info!");
            return false;
        }

        mDecoder.start();

        inputBuffers = mDecoder.getInputBuffers();
        //outputBuffers = mDecoder.getOutputBuffers();
        Log.e(TAG, "Decoder created");
        return true;

    }

    public void initDecoder(VideoStreamInfo videoinfo) {
        this.userStreamInfo = videoinfo;
        if (userStreamInfo.useDefaultConfig())
        {
           configDecoder();
        }
    }

    /* decode one h264 frame */
    private void decodeOneFrame(byte[] inBuf) {

        if (inBuf.length > 4) {

            if (mDecoder == null)
            {
                if((inBuf[Constants.RTP_AU_HEADER_LENGTH] & 0xFF & 0x1F)  == Constants.SPS_HEADER) {
                    userSps = new byte[inBuf.length];
                    System.arraycopy(inBuf,0,userSps,0,inBuf.length);
                    Log.e(TAG, "Got SPS");
                }
                if ((inBuf[Constants.RTP_AU_HEADER_LENGTH] & 0xFF & 0x1F)  == Constants.PPS_HEADER)
                {
                    userPps = new byte[inBuf.length];
                    System.arraycopy(inBuf,0,userPps,0,inBuf.length);
                    Log.e(TAG, "Got PPS");
                }

                if ((userPps != null) && (userPps != null))
                {
                    configDecoder();
                }
                else
                {
                    Log.e(TAG, "No available Sps and Pps info:"+inBuf[0]+":"+inBuf[3]+":"+inBuf[4]+":"+inBuf[5]);
                    return;
                }
            }

            int inputBufferIndex = mDecoder.dequeueInputBuffer(IN_DECODER_TIMEOUT);
            if (inputBufferIndex < 0) {
                Log.e(TAG, "No available Video codec input buffer");
                return;
            }
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();
            if (inputBuffer.capacity() >= inBuf.length) {
                inputBuffer.put(inBuf, 0, inBuf.length);
                mDecoder.queueInputBuffer(inputBufferIndex, 0, inBuf.length, 0, 0);
            } else {
                System.out.println("Input frame too long!" + inBuf.length);
            }

            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            int outputBufferIndex = -1;
            outputBufferIndex = mDecoder.dequeueOutputBuffer(info, OUT_DECODER_TIMEOUT);
            if (outputBufferIndex >= 0) {
                //we don't need the output data
                //surfaceview will get data by itself
                mDecoder.releaseOutputBuffer(outputBufferIndex, true);
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                //outputBuffers = mDecoder.getOutputBuffers();
                Log.w(TAG, "output buffers have changed.");
            } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                MediaFormat oformat = mDecoder.getOutputFormat();
                Log.w(TAG, "output format has changed to " + oformat);
            } else {
                Log.w(TAG, "dequeueOutputBuffer returned " + outputBufferIndex);
            }

        }
    }

    @Override
    public void run() {
        while (true) {
            byte[] frame = null;
            try {
                frame = outQueue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (frame != null)
                decodeOneFrame(frame);
        }
    }

    protected void finalize() {
        mDecoder.stop();
        mDecoder.release();
        mDecoder = null;
    }
}
